package playtools.core;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

import org.apache.commons.io.input.NullInputStream;

import playtools.util.LogUtil;

public class Proc {

	private ProcessBuilder builder;
	private InputStream preIn;
	private OutputStream preOut;
	private OutputStream preErr;

	private Process proc;
	private Thread copier1, copier2, copier3;

	/**
	 * @param err
	 *            null to redirect stderr to stdout.
	 */
	public Proc(String[] cmds, Map<String, String> envs, InputStream in,
			OutputStream out, OutputStream err, File workDir){
		this.builder = new ProcessBuilder(cmds);
		this.builder = environment(this.builder, envs);
		this.builder.directory(workDir);
		// this.builder.redirectErrorStream(false); //ʹ�ô�����Ϣ�ͱ�׼���һͬ���
		this.preErr = err;
		this.preIn = in;
		this.preOut = out;
	}

	private ProcessBuilder environment(ProcessBuilder pb,
			Map<String, String> env) {
		if (env != null) {
			Map<String, String> m = pb.environment();
			m.clear();
			m.putAll(env);
		}
		return pb;
	}

	private void start(String name) throws IOException {
		this.proc = this.builder.start();
		if (preIn == null)
			preIn = new NullInputStream(0);
		if (preOut == null)
			preOut = new ConsoleInfoStream();
		if (preErr == null)
			preErr = new ConsoleErrorStream();

		copier1 = new StdinCopyThread(name + ": stdout copier",
				proc.getInputStream(), preOut);
		copier1.start();

		copier2 = new StdinCopyThread(name + ": stdin copier", preIn,
				proc.getOutputStream());
		copier2.start();

		copier3 = new StdinCopyThread(name + ": stderr copier",
				proc.getErrorStream(), preErr);
		copier3.start();
	}

	/**
	 * Waits for the completion of the process.
	 */
	public int join(String name) {
		try {
			start(name);
			int r = proc.waitFor();
			copier1.join(10 * 1000);
			copier2.join(10 * 1000);
			copier3.join(10 * 1000);
			return r;
		} catch (Exception e) {
			return -1;
		}
	}

	private class ConsoleInfoStream extends OutputStream{
		byte[] buffer = new byte[8192];
	    int index = 0;
		public void write(int b) throws IOException {
		  if(index == buffer.length){
			  LogUtil.info(buffer, 0 ,buffer.length);
			  index = 0;
		  }
		  buffer[index++] = (byte) b;
		}
		
		public void flush(){
			LogUtil.info(buffer, 0, index);
			this.index = 0;
		}
		
	}
	private class ConsoleErrorStream extends OutputStream{
        byte[] buffer = new byte[8192];
        int index = 0;
        public void write(int b) throws IOException {
          if(index == buffer.length){
              LogUtil.error(buffer, 0 ,buffer.length);
              index = 0;
          }
          buffer[index++] = (byte) b;
        }
        
        public void flush(){
            LogUtil.error(buffer, 0, index);
            this.index = 0;
        }
        
    }
	/**
	 * {@link Process#getOutputStream()} is buffered, so we need to eagerly
	 * flash the stream to push bytes to the process.
	 */
	private class StdinCopyThread extends Thread {
		private final InputStream in;
		private final OutputStream out;

		public StdinCopyThread(String threadName, InputStream in,
				OutputStream out) {
			super(threadName);
			this.in = in;
			this.out = out;
		}

		public void run() {
			try {
				try {
					byte[] buf = new byte[8192];
					int len;
					while ((len = in.read(buf)) > 0) {
						out.write(buf, 0, len);
						out.flush();
					}
				} finally {
					in.close();
					out.close();
				}
			} catch (IOException e) {
				// TODO: what to do?
			}
		}
	}
}
